// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_BASE_SYNCED_PROPERTY_H_
#define CC_BASE_SYNCED_PROPERTY_H_

#include "base/memory/ref_counted.h"

namespace cc {

// This class is the basic primitive used for impl-thread scrolling.  Its job is
// to sanely resolve the case where both the main and impl thread are
// concurrently updating the same value (for example, when Javascript sets the
// scroll offset during an ongoing impl-side scroll).
//
// There are three trees (main, pending, and active) and therefore also three
// places with their own idea of the scroll offsets (and analogous properties
// like page scale).  Objects of this class are meant to be held on the Impl
// side, and contain the canonical reference for the pending and active trees,
// as well as keeping track of the latest delta sent to the main thread (which
// is necessary for conflict resolution).

template <typename T>
class SyncedProperty : public base::RefCounted<SyncedProperty<T>> {
public:
    SyncedProperty()
        : clobber_active_value_(false)
    {
    }

    // Returns the canonical value for the specified tree, including the sum of
    // all deltas.  The pending tree should use this for activation purposes and
    // the active tree should use this for drawing.
    typename T::ValueType Current(bool is_active_tree) const
    {
        if (is_active_tree)
            return active_base_.Combine(active_delta_).get();
        else
            return pending_base_.Combine(PendingDelta()).get();
    }

    // Sets the value on the impl thread, due to an impl-thread-originating
    // action.  Returns true if this had any effect.  This will remain
    // impl-thread-only information at first, and will get pulled back to the main
    // thread on the next call of PullDeltaToMainThread (which happens right
    // before the commit).
    bool SetCurrent(typename T::ValueType current)
    {
        T delta = T(current).InverseCombine(active_base_);
        if (active_delta_.get() == delta.get())
            return false;

        active_delta_ = delta;
        return true;
    }

    // Returns the difference between the last value that was committed and
    // activated from the main thread, and the current total value.
    typename T::ValueType Delta() const { return active_delta_.get(); }

    // Returns the latest active tree delta and also makes a note that this value
    // was sent to the main thread.
    typename T::ValueType PullDeltaForMainThread()
    {
        reflected_delta_in_main_tree_ = PendingDelta();
        return reflected_delta_in_main_tree_.get();
    }

    // Push the latest value from the main thread onto pending tree-associated
    // state.  Returns true if this had any effect.
    bool PushFromMainThread(typename T::ValueType main_thread_value)
    {
        bool changed = pending_base_.get() != main_thread_value;

        reflected_delta_in_pending_tree_ = reflected_delta_in_main_tree_;
        reflected_delta_in_main_tree_ = T::Identity();
        pending_base_ = T(main_thread_value);

        return changed;
    }

    // Push the value associated with the pending tree to be the active base
    // value.  As part of this, subtract the delta reflected in the pending tree
    // from the active tree delta (which will make the delta zero at steady state,
    // or make it contain only the difference since the last send).
    bool PushPendingToActive()
    {
        bool changed = active_base_.get() != pending_base_.get() || active_delta_.get() != PendingDelta().get();

        active_base_ = pending_base_;
        active_delta_ = PendingDelta();
        reflected_delta_in_pending_tree_ = T::Identity();
        clobber_active_value_ = false;

        return changed;
    }

    // This simulates the consequences of the sent value getting committed and
    // activated.
    void AbortCommit()
    {
        pending_base_ = pending_base_.Combine(reflected_delta_in_main_tree_);
        active_base_ = active_base_.Combine(reflected_delta_in_main_tree_);
        active_delta_ = active_delta_.InverseCombine(reflected_delta_in_main_tree_);
        reflected_delta_in_main_tree_ = T::Identity();
    }

    // Values as last pushed to the pending or active tree respectively, with no
    // impl-thread delta applied.
    typename T::ValueType PendingBase() const { return pending_base_.get(); }
    typename T::ValueType ActiveBase() const { return active_base_.get(); }

    // The new delta we would use if we decide to activate now.  This delta
    // excludes the amount that we know is reflected in the pending tree.
    T PendingDelta() const
    {
        if (clobber_active_value_)
            return T::Identity();
        return active_delta_.InverseCombine(reflected_delta_in_pending_tree_);
    }

    void set_clobber_active_value() { clobber_active_value_ = true; }
    bool clobber_active_value() const { return clobber_active_value_; }

private:
    // Value last committed to the pending tree.
    T pending_base_;
    // Value last committed to the active tree on the last activation.
    T active_base_;
    // The difference between |active_base_| and the user-perceived value.
    T active_delta_;
    // The value sent to the main thread on the last BeginMainFrame.  This is
    // always identity outside of the BeginMainFrame to (aborted)commit interval.
    T reflected_delta_in_main_tree_;
    // The value that was sent to the main thread for BeginMainFrame for the
    // current pending tree.  This is always identity outside of the
    // BeginMainFrame to activation interval.
    T reflected_delta_in_pending_tree_;
    // When true the pending delta is always identity so that it does not change
    // and will clobber the active value on push.
    bool clobber_active_value_;

    friend class base::RefCounted<SyncedProperty<T>>;
    ~SyncedProperty() { }
};

// SyncedProperty's delta-based conflict resolution logic makes sense for any
// mathematical group.  In practice, there are two that are useful:
// 1. Numbers/vectors with addition and identity = 0 (like scroll offsets)
// 2. Real numbers with multiplication and identity = 1 (like page scale)

template <class V>
class AdditionGroup {
public:
    typedef V ValueType;

    AdditionGroup()
        : value_(Identity().get())
    {
    }
    explicit AdditionGroup(V value)
        : value_(value)
    {
    }

    V& get() { return value_; }
    const V& get() const { return value_; }

    static AdditionGroup<V> Identity() { return AdditionGroup(V()); } // zero
    AdditionGroup<V> Combine(AdditionGroup<V> p) const
    {
        return AdditionGroup<V>(value_ + p.value_);
    }
    AdditionGroup<V> InverseCombine(AdditionGroup<V> p) const
    {
        return AdditionGroup<V>(value_ - p.value_);
    }

private:
    V value_;
};

class ScaleGroup {
public:
    typedef float ValueType;

    ScaleGroup()
        : value_(Identity().get())
    {
    }
    explicit ScaleGroup(float value)
        : value_(value)
    {
    }

    float& get() { return value_; }
    const float& get() const { return value_; }

    static ScaleGroup Identity() { return ScaleGroup(1.f); }
    ScaleGroup Combine(ScaleGroup p) const
    {
        return ScaleGroup(value_ * p.value_);
    }
    ScaleGroup InverseCombine(ScaleGroup p) const
    {
        return ScaleGroup(value_ / p.value_);
    }

private:
    float value_;
};

} // namespace cc

#endif // CC_BASE_SYNCED_PROPERTY_H_
